#!/usr/bin/env node

/*
 * Simple web server. Only handles GET requests (for now at least). Takes the
 * site's root directory as the first argument.
 *
 * TODO:
 * 	- Write a help message
 * 	- Implement proper error handling
 * 		- Remove die
 * 		- Use an approach closer to JavaScript
 * 	- Fallback method for updating 'files' on systems that don't report filename
 * 	- Optionally print diagnostic information (verbose flag)
*/

"use strict";

import path from 'node:path';
import argv from 'node:process';
import { createServer } from 'node:https';
import { readFileSync, readdirSync, existsSync, watch} from 'node:fs';

import { die } from './die.js';
import { getOpt }  from './getOpt.js';

function fetch(url) {
	let head = { statusCode: '', contentType: ''};	
	let contents = '';

	const notFoundMsg = `
	<!doctype html>
		<head>
			<meta charset="utf-8">
			<title>404: not found</title>
		</head>
		<body>
			<h1>404: not found</h1>
			<p>
			The server couldn't find the requested page. Possible reasons
			include:
			<ol>
			<li>Poorly formatted URL
			<li>The requested page doesn't yet, or will never, exist.
			<li>A server error has ocurred. In this case, the fault lies on the
			braindead developer (me) who coded it.
			</ol>
		</body>
	</html>`
	
	let baseUrl = path.basename(url);
	if (!baseUrl) baseUrl = 'index';
	
	if (baseUrl.endsWith('.css'))
		head.contentType = 'text/css';
	else
		head.contentType = 'text/html';
	
	const file = files.find(f => f.includes(baseUrl));
	if (file) {
		head.statusCode = 200;
		contents = readFileSync(path.join(root, file));
	} else {
		head.statusCode = 404;
		contents = notFoundMsg;
	}
	return [head, contents];
}

const opts = getOpt({ 
	verbose: { type: 'flag', short: 'v'},
	quiet: { type: 'flag', short: 'q'},
	key: { type: 'value', short: 'k'},
	cert: { type: 'value', short: 'c'},
	port: { 
		type: 'value', short: 'p', default: 8000,
		convert: v => parseInt(v), validate: v => v ? true : false,
	}}, { bundle: true });

const root = process.argv.at(-1);

// Test if files exist
if (!existsSync(opts.key)) die("Couldn't stat private-key");
if (!existsSync(opts.cert)) die("Couldn't stat certificate");
if (!existsSync(root)) die("Couldn't stat root dir");

const files = readdirSync(root, { recursive: true });
const options = {
	key: readFileSync(opts.key),
	cert: readFileSync(opts.cert)
};

// Set up a watch to update the 'files' array on directory change
if (opts.verbose) console.log('Setting up watches...');
watch(root, { recursive: true }, (eventType, file) => {
	if (!file) 
		die("System don't support reporting filename on directory change");
	if (eventType == 'rename') {
		if (opts.verbose) console.log('Detected rename event.');
		if (files.includes(file)) {
			if (opts.verbose) console.log(`Removing file ${file} to tree`);
			files.splice(files.indexOf(file), 1);
		} else {
			if (opts.verbose) console.log(`Addding file ${file} to tree`);
			files.push(file);
		}
	}
});

const server = createServer(options, (req, res) => {
	if (opts.verbose) console.log(`Fetching URL: ${req.url}`);
	let [head, contents] = fetch(decodeURIComponent(req.url));
	res.writeHead(head.statusCode, {'Content-Type': head.contentType});
	res.write(contents);
	res.end();
});

if (!opts.quiet) console.log(`Starting server at port ${opts.port}...`);
server.listen(opts.port);
